//
//  TTDatabaseObject.m
//  Tint
//
//  Created by Kevin Wu on 5/30/12.
//  Copyright (c) 2012 Telligenty. All rights reserved.
//

#import "TTDatabaseObject.h"
#import <objc/runtime.h>
#import "TTDatabase.h"


@implementation TTDatabaseObject


#pragma mark - Accessors

@synthesize pk = _pk;


#pragma mark - Memory

- (id)init {
    self = [super init];
    if (self) {
        _pk = 0;
    }
    return self;
}

- (void)dealloc {
    _pk = 0;
    [super dealloc];
}


#pragma mark - Public


+ (BOOL)createTable {
    TTDatabase *db = [TTDatabase sharedObject];
    if ([db hasTable:[self classString]]) return YES;
    
    
    NSMutableArray *components = [NSMutableArray array];
    
    NSArray *propertyNames = [[self class] propertyNames];
    
    for (int i=0; i<[propertyNames count]; ++i) {
        NSString *name = [propertyNames objectAtIndex:i];
        NSString *attribute = [self attributeForProperty:name];
        
        if ([attribute hasPrefix:@"Tc"]) { // BOOL: Tc
            NSString *component = [NSString stringWithFormat:@"%@ INTEGER", name];
            [components addObject:component];
        } else if ([attribute hasPrefix:@"Ti"]) { // int: Ti
            NSString *component = [NSString stringWithFormat:@"%@ INTEGER", name];
            [components addObject:component];
        } else if ([attribute hasPrefix:@"Tq"]) { // long long: Tq
            NSString *component = [NSString stringWithFormat:@"%@ INTEGER", name];
            [components addObject:component];
        } else if ([attribute hasPrefix:@"Td"]) { // double: Td
            NSString *component = [NSString stringWithFormat:@"%@ REAL", name];
            [components addObject:component];
        } else if ([attribute hasPrefix:@"T@\"NSString\""]) { // NSString: T@"NSString"
            NSString *component = [NSString stringWithFormat:@"%@ TEXT", name];
            [components addObject:component];
        } else if ([attribute hasPrefix:@"T@\"NSDate\""]) { // NSDate: T@"NSDate"
            NSString *component = [NSString stringWithFormat:@"%@ REAL", name];
            [components addObject:component];
        }
        
    }
    
    if (TTIsArrayWithItems(components)) {
        
        [components insertObject:@"pk INTEGER PRIMARY KEY" atIndex:0];
        
        NSString *sql = [NSString stringWithFormat:@"CREATE TABLE %@(%@);", 
                         [self classString], 
                         [components componentsJoinedByString:@", "]];
        
        return [db executeUpdateString:sql];
        
    }
    
    return NO;
}



- (BOOL)save {
    NSArray *fields = [[self class] propertyNames];
    
    if (TTIsArrayWithItems(fields))
        return [self save:[fields componentsJoinedByString:@","]];
    
    return NO;
}

- (BOOL)saveExcept:(NSString *)fieldString {
    NSArray *tmp = [[fieldString componentsSeparatedByString:@","] nonEqualObjectsArray];
    NSMutableArray *exceptFields = [NSMutableArray array];
    for (NSString *field in tmp) {
        [exceptFields addObjectIfNotNil:[field stringByRemovingWhitespace]];
    }
    
    NSMutableArray *fields = [NSMutableArray arrayWithArray:[[self class] propertyNames]];
    [fields removeObjectsInArray:exceptFields];
    
    if (TTIsArrayWithItems(fields))
        return [self save:[fields componentsJoinedByString:@","]];
    
    return NO;
}

- (BOOL)save:(NSString *)fieldString {
    
    NSArray *tmp = [[fieldString componentsSeparatedByString:@","] nonEqualObjectsArray];
    NSMutableArray *fields = [NSMutableArray array];
    for (NSString *field in tmp) {
        [fields addObjectIfNotNil:[field stringByRemovingWhitespace]];
    }
    if (!TTIsArrayWithItems(fields)) return NO;
    
    
    NSMutableArray *components = [NSMutableArray array];
    NSMutableArray *placeholders = [NSMutableArray array];
    NSMutableArray *values = [NSMutableArray array];
    
    NSArray *propertyNames = [[self class] propertyNames];
    
    for (int i=0; i<[propertyNames count]; ++i) {
        NSString *name = [propertyNames objectAtIndex:i];
        NSString *attribute = [[self class] attributeForProperty:name];
        
        if (![fields containsObject:name]) continue;
        
        if ([attribute hasPrefix:@"Tc"]) { // BOOL: Tc
            [components addObject:name];
            [placeholders addObject:@"?"];
            NSNumber *value = [self valueForKey:name];
            if (value == nil) value = [NSNumber numberWithBool:NO];
            [values addObject:value];
        } else if ([attribute hasPrefix:@"Ti"]) { // int: Ti
            [components addObject:name];
            [placeholders addObject:@"?"];
            NSNumber *value = [self valueForKey:name];
            if (value == nil) value = [NSNumber numberWithInt:0];
            [values addObject:value];
        } else if ([attribute hasPrefix:@"Tq"]) { // long long: Tq
            [components addObject:name];
            [placeholders addObject:@"?"];
            NSNumber *value = [self valueForKey:name];
            if (value == nil) value = [NSNumber numberWithLongLong:0];
            [values addObject:value];
        } else if ([attribute hasPrefix:@"Td"]) { // double: Td
            [components addObject:name];
            [placeholders addObject:@"?"];
            NSNumber *value = [self valueForKey:name];
            if (value == nil) value = [NSNumber numberWithDouble:0.0];
            [values addObject:value];
        } else if ([attribute hasPrefix:@"T@\"NSString\""]) { // NSString: T@"NSString"
            [components addObject:name];
            [placeholders addObject:@"?"];
            NSString *value = [self valueForKey:name];
            if (!TTIsStringWithText(value)) value = @"";
            [values addObject:value];
        } else if ([attribute hasPrefix:@"T@\"NSDate\""]) { // NSDate: T@"NSDate"
            [components addObject:name];
            [placeholders addObject:@"?"];
            NSDate *value = [self valueForKey:name];
            if (!TTIsInstance(value, [NSDate class])) value = [NSDate dateWithTimeIntervalSince1970:0.0];
            [values addObject:value];
        }
        
    }
    
    
    if (TTIsArrayWithItems(components) && 
        TTIsArrayWithItems(placeholders) && 
        TTIsArrayWithItems(values) && 
        (([components count] == [placeholders count]) && (([placeholders count] == [values count])))) {
        
        
        if ([self isExist]) {
            
            NSMutableArray *tmp = [NSMutableArray array];
            for (int i=0; i<[components count]; ++i) {
                NSString *component = [components objectAtIndex:i];
                [tmp addObject:[component stringByAppendingString:@"=?"]];
            }
            
            [values addObject:[NSNumber numberWithInt:_pk]];
            
            NSString *sql = [NSString stringWithFormat:@"UPDATE %@ SET %@ WHERE pk=?;", 
                             [self classString], 
                             [tmp componentsJoinedByString:@", "]];
            return [[TTDatabase sharedObject] executeUpdate:sql parameters:values];
            
        } else {
            
            if (_pk>0) {
                [components addObject:@"pk"];
                [placeholders addObject:@"?"];
                [values addObject:[NSNumber numberWithInt:_pk]];
            }
            
            NSString *sql = [NSString stringWithFormat:@"INSERT INTO %@(%@) VALUES(%@);", 
                             [self classString], 
                             [components componentsJoinedByString:@", "], 
                             [placeholders componentsJoinedByString:@", "]];
            sqlite3_int64 rowid = [[TTDatabase sharedObject] executeInsert:sql parameters:values];
            
            if (rowid>0) {
                self.pk = rowid;
                return YES;
            }
            
        }
        
        
    }
    
    return NO;
}


- (void)destroy {
    NSString *sql = [NSString stringWithFormat:@"DELETE FROM %@ WHERE pk=?;", [self classString]];
    [[TTDatabase sharedObject] executeUpdate:sql, [NSNumber numberWithInt:_pk], nil];
}


- (BOOL)isExist {
    NSArray *objects = [[self class] selectObjectsOrderBy:nil where:@"pk=?", [NSNumber numberWithInt:_pk], nil];
    return TTIsArrayWithItems(objects);
}


- (void)reload {
    NSArray *objects = [[self class] selectObjectsOrderBy:nil where:@"pk=?", [NSNumber numberWithInt:_pk], nil];
    id object = [objects objectOrNilAtIndex:0];
    if (object == nil) return;
    
    NSArray *propertyNames = [[self class] propertyNames];
    for (NSString *name in propertyNames) {
        [self setValue:[object valueForKeyPath:name] forKey:name];
    }
}


+ (NSUInteger)count {
    return [self countWhere:nil];
}

+ (NSUInteger)countWhere:(NSString *)where, ... {
    NSMutableString *sql = [NSMutableString stringWithFormat:@"SELECT * FROM %@", [self classString]];
    
    if (TTIsStringWithText(where))
        [sql appendFormat:@" WHERE %@", where];
    
    [sql appendString:@";"];
    
    
    
    NSMutableArray *valistArray = nil;
    
    if (TTIsStringWithText(where)) {
        valistArray = [NSMutableArray array];
        va_list arguments;
        va_start(arguments, where);
        id object = nil;
        while ((object = va_arg(arguments, id)))
            [valistArray addObject:object];
        va_end(arguments);
    }
    
    
    TTDatabaseResult *result = [[TTDatabase sharedObject] executeQuery:sql parameters:valistArray];
    return [result count];
}

+ (BOOL)deleteAllObjects {
    return [self deleteObjectsWhere:nil];
}

+ (BOOL)deleteObjectsWhere:(NSString *)where, ... {
    
    NSMutableString *sql = [NSMutableString stringWithFormat:@"DELETE FROM %@", [self classString]];
    
    if (TTIsStringWithText(where))
        [sql appendFormat:@" WHERE %@", where];
    
    [sql appendString:@";"];
    
    
    NSMutableArray *valistArray = nil;
    
    if (TTIsStringWithText(where)) {
        valistArray = [NSMutableArray array];
        va_list arguments;
        va_start(arguments, where);
        id object = nil;
        while ((object = va_arg(arguments, id)))
            [valistArray addObject:object];
        va_end(arguments);
    }
    
    return [[TTDatabase sharedObject] executeUpdate:sql parameters:valistArray];
}


+ (BOOL)updateAllObjectsSet:(NSString *)set {
    return [self updateObjectsSet:set where:nil];
}

+ (BOOL)updateObjectsSet:(NSString *)set where:(NSString *)where, ... {
    
    if (!TTIsStringWithText(set)) return NO;
    
    NSMutableString *sql = [NSMutableString stringWithFormat:@"UPDATE %@ SET %@", [self classString], set];
    
    if (TTIsStringWithText(where))
        [sql appendFormat:@" WHERE %@", where];
    
    [sql appendString:@";"];
    
    
    
    NSMutableArray *valistArray = nil;
    
    if (TTIsStringWithText(where)) {
        valistArray = [NSMutableArray array];
        va_list arguments;
        va_start(arguments, where);
        id object = nil;
        while ((object = va_arg(arguments, id)))
            [valistArray addObject:object];
        va_end(arguments);
    }
    
    return [[TTDatabase sharedObject] executeUpdate:sql parameters:valistArray];
}




+ (id)firstObject {
    return [[self selectAllObjects] firstObject];
}

+ (id)firstObjectOrderBy:(NSString *)orderBy {
    return [[self selectObjectsOrderBy:orderBy] firstObject];
}

+ (id)firstObjectWhere:(NSString *)where, ... {
    
    NSMutableArray *valistArray = nil;
    
    if (TTIsStringWithText(where)) {
        valistArray = [NSMutableArray array];
        va_list arguments;
        va_start(arguments, where);
        id object = nil;
        while ((object = va_arg(arguments, id)))
            [valistArray addObject:object];
        va_end(arguments);
    }
    
    return [[self selectObjectsOrderBy:nil where:where parameters:valistArray] firstObject];
}

+ (id)firstObjectOrderBy:(NSString *)orderBy where:(NSString *)where, ... {
    
    NSMutableArray *valistArray = nil;
    
    if (TTIsStringWithText(where)) {
        valistArray = [NSMutableArray array];
        va_list arguments;
        va_start(arguments, where);
        id object = nil;
        while ((object = va_arg(arguments, id)))
            [valistArray addObject:object];
        va_end(arguments);
    }
    
    return [[self selectObjectsOrderBy:orderBy where:where parameters:valistArray] firstObject];
}



+ (NSArray *)selectAllObjects {
    return [self selectObjectsOrderBy:nil where:nil];
}

+ (NSArray *)selectObjectsOrderBy:(NSString *)orderBy {
    return [self selectObjectsOrderBy:orderBy where:nil];
}

+ (NSArray *)selectObjectsWhere:(NSString *)where, ... {
    
    NSMutableArray *valistArray = nil;
    
    if (TTIsStringWithText(where)) {
        valistArray = [NSMutableArray array];
        va_list arguments;
        va_start(arguments, where);
        id object = nil;
        while ((object = va_arg(arguments, id)))
            [valistArray addObject:object];
        va_end(arguments);
    }
    
    return [self selectObjectsOrderBy:nil where:where parameters:valistArray];
}

+ (NSArray *)selectObjectsOrderBy:(NSString *)orderBy where:(NSString *)where, ... {
    
    NSMutableArray *valistArray = nil;
    
    if (TTIsStringWithText(where)) {
        valistArray = [NSMutableArray array];
        va_list arguments;
        va_start(arguments, where);
        id object = nil;
        while ((object = va_arg(arguments, id)))
            [valistArray addObject:object];
        va_end(arguments);
    }
    
    return [self selectObjectsOrderBy:orderBy where:where parameters:valistArray];
}

+ (NSArray *)selectObjectsOrderBy:(NSString *)orderBy where:(NSString *)where parameters:(NSArray *)parameters {
    
    NSMutableString *sql = [NSMutableString stringWithFormat:@"SELECT * FROM %@", [self classString]];
    
    if (TTIsStringWithText(where))
        [sql appendFormat:@" WHERE %@", where];
    
    if (TTIsStringWithText(orderBy))
        [sql appendFormat:@" ORDER BY %@", orderBy];
    
    [sql appendString:@";"];
    
    
    
    TTDatabaseResult *result = [[TTDatabase sharedObject] executeQuery:sql parameters:parameters];
    
    NSMutableArray *objects = [NSMutableArray array];
    
    for (int i=0; i<[result count]; ++i) {
        TTDatabaseRow *row = [result rowAtIndex:i];
        id object = [self objectWithDatabaseRow:row];
        [object setValue:[NSNumber numberWithInt:[row intForColumn:@"pk"]] forKey:@"pk"];
        [objects addObjectIfNotNil:object];
    }
    
    if (TTIsArrayWithItems(objects))
        return [NSArray arrayWithArray:objects];
    
    return nil;
}


@end
